/*
 * Copyright 2011 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.coding.program.login.auth;

/**
 * Counter whose value is a deterministic function of time as described in RFC 6238
 * "TOTP: Time-Based One-Time Password Algorithm".
 * <p/>
 * <p>The 64-bit counter assumes the value {@code 0} at a predefined point in time and periodically
 * increments its value by one periodically.
 * <p/>
 * <p>The value {@code V} of the counter at time instant {@code T} is:
 * <pre>
 * V = (T - T0) / TimeStep
 * </pre>
 * where {@code T0} is the earliest time instant at which the counter assumes the value {@code 0},
 * and {@code TimeStep} is the duration of time for which the values of the counter remain constant.
 * <p/>
 * <p><em>Note: All time instants are in seconds since UNIX epoch, and all time durations are
 * in seconds.</em>
 * <p/>
 * <p><em>Note: All time instants must be non-negative.</em>
 * <p/>
 * <p>Thread-safety: Instances of this class are immutable and are thus thread-safe.
 *
 * @author klyubin@google.com (Alex Klyubin)
 */
public class TotpCounter {

    /**
     * Interval of time (seconds) between successive changes of this counter's value.
     */
    private final long mTimeStep;

    /**
     * Earliest time instant (seconds since UNIX epoch) at which this counter assumes the value of
     * {@code 0}.
     */
    private final long mStartTime;

    /**
     * Constructs a new {@code TotpCounter} that starts with the value {@code 0} at time instant
     * {@code 0} (seconds since UNIX epoch) and increments its value with the specified frequency.
     *
     * @param timeStep interval of time (seconds) between successive changes of this counter's value.
     */
    public TotpCounter(long timeStep) {
        this(timeStep, 0);
    }

    /**
     * Constructs a new {@code TotpCounter} that starts with the value {@code 0} at the specified
     * time and increments its value with the specified frequency.
     *
     * @param timeStep  interval of time (seconds) between successive changes of this counter's value.
     * @param startTime the earliest time instant (seconds since UNIX epoch) at which this counter
     *                  assumes the value {@code 0}.
     */
    public TotpCounter(long timeStep, long startTime) {
        if (timeStep < 1) {
            throw new IllegalArgumentException("Time step must be positive: " + timeStep);
        }
        assertValidTime(startTime);

        mTimeStep = timeStep;
        mStartTime = startTime;
    }

    private static void assertValidTime(long time) {
        if (time < 0) {
            throw new IllegalArgumentException("Negative time: " + time);
        }
    }

    /**
     * Gets the frequency with which the value of this counter changes.
     *
     * @return interval of time (seconds) between successive changes of this counter's value.
     */
    public long getTimeStep() {
        return mTimeStep;
    }

    /**
     * Gets the earliest time instant at which this counter assumes the value {@code 0}.
     *
     * @return time (seconds since UNIX epoch).
     */
    public long getStartTime() {
        return mStartTime;
    }

    /**
     * Gets the value of this counter at the specified time.
     *
     * @param time time instant (seconds since UNIX epoch) for which to obtain the value.
     * @return value of the counter at the {@code time}.
     */
    public long getValueAtTime(long time) {
        assertValidTime(time);

        // According to the RFC:
        // T = (Current Unix time - T0) / X, where the default floor function is used.
        //   T  - counter value,
        //   T0 - start time.
        //   X  - time step.

        // It's important to use a floor function instead of simple integer division. For example,
        // assuming a time step of 3:
        // Time since start time: -6 -5 -4 -3 -2 -1  0  1  2  3  4  5  6
        // Correct value:         -2 -2 -2 -1 -1 -1  0  0  0  1  1  1  2
        // Simple division / 3:   -2 -1 -1 -1  0  0  0  0  0  1  1  1  2
        //
        // To avoid using Math.floor which requires imprecise floating-point arithmetic, we
        // we compute the value using integer division, but using a different equation for
        // negative and non-negative time since start time.
        long timeSinceStartTime = time - mStartTime;
        if (timeSinceStartTime >= 0) {
            return timeSinceStartTime / mTimeStep;
        } else {
            return (timeSinceStartTime - (mTimeStep - 1)) / mTimeStep;
        }
    }

    /**
     * Gets the time when the counter assumes the specified value.
     *
     * @param value value.
     * @return earliest time instant (seconds since UNIX epoch) when the counter assumes the value.
     */
    public long getValueStartTime(long value) {
        return mStartTime + (value * mTimeStep);
    }
}
